package qqsdk

import (
	"crypto"
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net"
	"os/exec"
	"reflect"
	"sort"
	"strings"
	"time"
)

type QQSDKOption struct {
	AppKey       string
	Version      string
	SecretKey    string
	AesKey       string
	AesIv        string
	RsaPublicKey string
	EncryptMode  int
}
type KickedData struct {
	BaseResp
	IsKicked bool `json:"isKicked,omitempty"`
}
type QQSDK struct {
	HttpRequest
	QQSDKOption
	User              string
	Password          string
	Client            string
	Mac               string
	HeartBeatCallback func(UserHeart)
	HeartErrno        []int
	KickedData        KickedData
}

func NewQQSDK(option QQSDKOption, heartBeatCallback func(UserHeart)) *QQSDK {
	sdk := QQSDK{
		QQSDKOption:       option,
		HeartBeatCallback: heartBeatCallback,
	}
	sdk.SetUrl("http://app.88x0.com")
	//sdk.SetProxy("http://127.0.0.1:8899")
	sdk.Mac = sdk.GetDeviceID()
	return &sdk
}

func (sdk *QQSDK) GetTime() (resp TimeStampResp) {
	param := map[string]interface{}{
		"action": "soft.timestamp",
	}
	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) GetSoftInfo() (resp SoftInfoResp) {
	param := map[string]interface{}{
		"action": "soft.init",
	}
	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) UserRegister(user, pwd, pwd2, key, recommenber string) (resp UserRegisterResp) {
	param := map[string]interface{}{
		"action":      "user.register",
		"user":        user,
		"pwd":         pwd,
		"pwd2":        pwd2,
		"key":         key,
		"recommenber": recommenber,
	}
	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) UserLogin(user, pwd string) (resp UserLoginResp, ok bool) {
	param := map[string]interface{}{
		"action": "user.login",
		"user":   user,
		"pwd":    pwd,
	}

	sdk.Client = ""
	sdk.Http(param, &resp)
	if resp.Errno == 0 {
		sdk.User = user
		sdk.Password = pwd
		ok = true
		sdk.Client = resp.Data.Client
		sdk.StartHeart()
	} else {
		log.Println(resp.Errmsg)
	}
	return
}

func (sdk *QQSDK) ReloadLogin() (resp UserLoginResp, ok bool) {
	return sdk.UserLogin(sdk.User, sdk.Password)
}

func (sdk *QQSDK) KeyLogin(key string) (resp UserLoginResp, ok bool) {
	param := map[string]interface{}{
		"action": "user.login",
		"user":   key,
		"pwd":    key,
	}
	sdk.Client = ""
	sdk.Http(param, &resp)
	if resp.Errno == 0 {
		ok = true
		sdk.Client = resp.Data.Client
		sdk.StartHeart()
	} else {
		log.Println(resp.Errmsg)
	}
	return
}

func (sdk *QQSDK) Logout() (resp UserLogoutResp) {
	param := map[string]interface{}{
		"action": "user.logout",
		"client": sdk.Client,
		"type":   0,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) UserUnbind(user, key string) (resp UserUnBindResp) {
	param := map[string]interface{}{
		"action": "user.bind",
		"user":   key,
		"pwd":    key,
	}
	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) GetVar(label string) (resp GetVarResp) {
	param := map[string]interface{}{
		"action": "user.getVar",
		"label":  label,
		"client": sdk.Client,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) RunJs(symbol string) (resp RunJsResp) {
	param := map[string]interface{}{
		"action": "user.saas",
		"symbol": symbol,
		"client": sdk.Client,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) CutPoints(user, pwd string, number int) (resp UserCutPointResp) {
	param := map[string]interface{}{
		"action": "user.points",
		"number": number,
		"user":   user,
		"pwd":    pwd,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) ExtendsGet(user, pwd string) (resp GetUserExtendsResp) {
	param := map[string]interface{}{
		"action": "user.extends.get",
		"user":   user,
		"pwd":    pwd,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) ExtendsPut(user, pwd string, extend map[string]interface{}) (resp SetUserExtendsResp) {
	if extend == nil {
		log.Println("extend扩展属性必须为JSON对象")
		resp = SetUserExtendsResp{BaseResp{Errno: 400, Errmsg: "extend扩展属性必须为JSON对象"}}
		return
	}

	param := map[string]interface{}{
		"action": "user.extends.put",
		"user":   user,
		"pwd":    pwd,
		"extend": extend,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) UserRecharge(user, key string) (resp UserRechargeResp) {
	param := map[string]interface{}{
		"action": "user.recharge",
		"user":   user,
		"key":    key,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) GetDeviceID() string {
	cpuId := getCPUID()
	cpuName := getCPUName()
	gpuId := getGPUID()
	gpuName := getGPUName()
	mac := getMACAddress()
	deviceID := cpuId + cpuName + sdk.AppKey + gpuId + gpuName + sdk.SecretKey + mac
	id := md5.Sum([]byte(deviceID))
	deviceID = fmt.Sprintf("%x", id)
	return deviceID
}

func (sdk *QQSDK) UserHeart() (resp UserHeart) {
	param := map[string]interface{}{
		"action": "user.heart",
		"client": sdk.Client,
	}

	sdk.Http(param, &resp)
	return
}

func (sdk *QQSDK) StartHeart() {
	go func() {
		for {
			result := sdk.UserHeart()
			/* 			if result.Errno != 0 && inSlice(result.Errno, sdk.HeartErrno) {
			   				sdk.HeartBeatCallback(result)
			   				break
			   			} else if result.Errno != 0 {
			   				sdk.HeartBeatCallback(result)
			   				break
			   			} */
			sdk.HeartBeatCallback(result)
			if result.Errmsg == "您的会员账号当前已在其它机器登录" || result.Errmsg == "请确认时间是否正确设置" {
				sdk.KickedData = KickedData{
					BaseResp: result.BaseResp,
					IsKicked: true,
				}

				return
			}
			time.Sleep(time.Second * 10)
		}
	}()
}

func (sdk *QQSDK) Unix() int64 {
	return time.Now().Unix()
}

func (sdk *QQSDK) JoinParams(params map[string]interface{}) string {
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	ps := make([]string, 0, len(params))
	for _, k := range keys {
		ps = append(ps, fmt.Sprintf("%s=%v", k, params[k]))
	}

	return strings.Join(ps, "&")
}

func (sdk *QQSDK) Param(params map[string]interface{}) map[string]interface{} {
	timestamp := sdk.Unix()
	params["appkey"] = sdk.AppKey
	params["version"] = sdk.Version
	params["timestamp"] = timestamp
	params["mac"] = sdk.Mac
	params["encrypt"] = sdk.EncryptMode
	params["sign"] = sdk.sign(timestamp)
	//params["random"] = randStr(34)

	return params
}

func (sdk *QQSDK) sign(timestamp int64) string {
	str := sdk.AppKey + sdk.SecretKey + sdk.Version + fmt.Sprintf("%d", timestamp) + sdk.Mac //md5(appkey + secretkey + version + timestamp + mac)
	return Md5(str)
}

func (sdk *QQSDK) Http(params map[string]interface{}, resp IResp) {
	params = sdk.Param(params)
	if sdk.EncryptMode == 1 {
		js, err := json.Marshal(params)
		ciphertext, err := AesEncrypt(js, sdk.AesKey, sdk.AesIv)
		if err != nil {
			resp.SetErrNo(400, "网络或服务器异常")
			return
		}
		signal := Md5(sdk.AppKey)
		params = map[string]interface{}{
			"sign":       params["sign"],
			"signal":     signal,
			"encrypt":    Md5(fmt.Sprintf("%d%s", sdk.EncryptMode, signal)),
			"ciphertext": ciphertext,
			"timestamp":  params["timestamp"],
		}
	}
	sdk.Post("/api/index/", params)
	if sdk.resp.StatusCode == 0 {
		resp.SetErrNo(400, "网络或服务器异常")
		return
	}
	text := sdk.resp.Text
	result := sdk.BodyToMap()
	if v, ok := result["errno"]; ok {
		if v.(int) == 0 {
			cc, _ := base64.StdEncoding.DecodeString(result["data"].(string))
			dat, err := AesDecrypt(string(cc), sdk.AesKey, sdk.AesIv)
			cc, _ = base64.StdEncoding.DecodeString(dat)
			dat = string(cc)
			if err != nil {
				resp.SetErrNo(400, "网络或服务器异常")
				return
			}
			if RSASignVerify(sdk.RsaPublicKey, Md5(dat), result["signal"].(string), crypto.MD5) {
				text = dat
			}
		} else if v.(int) == 400 {
			resp.SetErrNo(400, result["errmsg"].(string))
			return
		} else {
			resp.SetErrNo(400, result["errmsg"].(string))
			return
		}
	}

	err := json.Unmarshal([]byte(text), resp)
	if err != nil {
		fmt.Println(err)
		resp.SetErrNo(400, "数据错误")
		return
	}
	if math.Abs(float64(time.Now().UnixMilli()-resp.GetTimestamp())) > 12000 {
		resp.SetErrNo(400, "请确认时间是否正确设置")
		return
	}
	return
}

func inSlice(n int, slice []int) bool {
	for _, v := range slice {
		if v == n {
			return true
		}
	}
	return false
}

func randStr(n int) string {
	const alphanum = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	var bytes = make([]byte, n)
	rand.Read(bytes)
	for i, b := range bytes {
		bytes[i] = alphanum[b%byte(len(alphanum))]
	}
	return string(bytes)
}

func getCPUName() string {
	cmd := exec.Command("wmic", "cpu", "get", "Name")
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Failed to get CPU name:", err)
		return ""
	}

	cpuInfo := strings.Split(string(output), "\n")[1]
	return strings.TrimSpace(cpuInfo)
}

func getGPUName() string {
	cmd := exec.Command("wmic", "path", "win32_VideoController", "get", "Name")
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Failed to get GPU name:", err)
		return ""
	}

	gpuInfo := strings.Split(string(output), "\n")[1]
	return strings.TrimSpace(gpuInfo)
}

func getCPUID() string {
	cmd := exec.Command("wmic", "cpu", "get", "ProcessorID")
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Failed to get CPU ID:", err)
		return ""
	}
	cpuID := strings.Split(string(output), "\n")[1]
	return strings.TrimSpace(cpuID)
}

func getGPUID() string {
	cmd := exec.Command("wmic", "path", "win32_VideoController", "get", "DeviceID")
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Failed to get GPU ID:", err)
		return ""
	}

	gpuID := strings.Split(string(output), "\n")[1]
	return strings.TrimSpace(gpuID)
}

func getMACAddress() string {
	interfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("Failed to get MAC address:", err)
		return ""
	}

	for _, iface := range interfaces {
		if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 {
			mac := iface.HardwareAddr.String()
			if mac != "" {
				return mac
			}
		}
	}

	fmt.Println("MAC address not found")
	return ""
}

func MapToStruct(data map[string]interface{}, result interface{}) error {
	structValue := reflect.ValueOf(result).Elem()
	structType := structValue.Type()

	for i := 0; i < structType.NumField(); i++ {
		field := structType.Field(i)
		tag := field.Tag.Get("map")

		if tag != "" {
			if value, ok := data[tag]; ok {
				fieldValue := structValue.Field(i)

				if reflect.TypeOf(value) == fieldValue.Type() {
					fieldValue.Set(reflect.ValueOf(value))
				} else {
					return fmt.Errorf("type mismatch for field %s", field.Name)
				}
			}
		}
	}

	return nil
}

func Md5(str string) string {
	b := md5.Sum([]byte(str))
	return fmt.Sprintf("%x", b)
}
